10. The Library Maker |Table of Contents

 

Chapter 11

The Source Code Debugger

Introduction
Debugger Control
STOP Commands and Breakpoints
Memory Windows
Variable Windows
Debugger Settings
Debugger Error Messages
Practical Examples


Introduction

If you have written computer programs already, you are probably familiar with the problem that a program can behave differently than expected. It is usually rather difficult to find a simple typo or even a systematic error without some sort of aid because the program's processing is for the most part not visible.
The source code debugger now gives you the means to search for specific errors. Just halt your program when suitable and then work forward in single steps. You can then monitor how variables or memory content is changing and thus determine exactly at which location your program exhibits unexpected behavior. A small triangle in the source code always shows the current program pointer position when debugging.
Moreover, the source code debugger is also well suited for didactic purposes. Instructors can use the individual steps of the debugger to teach their students about the function of an algorithm.
For this purpose, the animation mode can be used as well, which executes a program in slow motion, showing every individual step in detail.




Debugger Control

To be able to debug a program, you first have to move the window with the source code to the front and then select the menu option 'Debug' from the 'Program' menu. The program is then compiled in a special format, which enables the debugger to monitor and control the program execution at any time. The following potential error sources are monitored as well:

- Integer overflows with byte, integer, and long integer numbers. For example: A%=32768 or A%L=10^12 would trigger such an error.

- Floating point underflows and floating point overflows. These errors occur when the result of a calculation cannot be depicted in the applied number format (e.g., A#=10^100*10^250 or A!=10^-45).

- Divisions by zero.

- Illegal operations such as A#=LN(-1).

- BASIC stack overflow. This can happen, e.g., when sorting large fields or during recursive programming with large interleaf depths.

- In case of field variables, it is checked whether the specified indices are located in the range for which the field has been dimensioned, e.g., DIM A#(2,3):A#(3,2)=1 would trigger such an error.

If one of these errors occurs, an alert box is shown first describing the error. After leaving the alert box the Debugger Control appears. You can now open memory and variable windows and find out what caused the error. If it is not a fatal error, the program may continue; otherwise, it is always terminated when you try to continue.

Of course, there is a variety of other traps waiting for the programmer, which cannot be recognized by the debugger due to their nature (e.g., LPOKE 12,-1, or if you are using the Extension Library but have forgotten to add Extension_Init to the beginning of the program). Do not try this because it is sure to crash your computer!

For the debugger to be able to process all of these tasks, a table is created establishing a link between the source code and the object code generated by the compiler. The program can thus not be edited during the debugging phase. Therefore, do not think that your computer has crashed just because you cannot change your program any longer.

The program is started automatically after compilation is complete. The programs generated with 'Debug' run somewhat slower than when using the menu option 'Compile & Start' due to the many management tasks that have to be processed. However, this should not come into play when searching for errors.

Note: If you are using the Lite or the Demo version it may be that a program can be compiled normally but cannot be debugged. The reason for this is the additional object code generated in the debugging mode, which results in a program that is too long for the Lite or Demo version.

After the debugger has started your program, a small window opens containing the Debugger Control. The window has 7 buttons, which may be used to control the progress of your program. The control options are as follows:


[Stop]
Your program comes to an immediate stop if you click on this button. The window with the source code also automatically scrolls to the spot where the program stopped and marks this location with a small triangle. This function can also be called by pressing [Ctrl]+[C].

Attention: If your program does not make any regular calls of COMPILER "EVENT" or EasyMesag (in the case of programming with EasyGem) the program can be stopped only by pressing [Ctrl]+[C].


[Continue]
This button can be selected only if a program has been stopped. When you click on this button, the program execution is continued with the command to which the small triangle is pointing.


[Step into]
This button can be selected only if a program has been stopped. When you click on this button, only the one command to which the small triangle is pointing is executed. If it is a procedure or function call, the program pointer jumps into the procedure or function. The same applies to GOSUB commands. Please note that functions can also occur in formulas. In that case, all functions occurring in the formula are entered into.

If the procedure or function has been supplied by a library, the debugger does not enter the subprogram. This would actually serve no purpose since the program code in the library is not listed and you cannot monitor the progress of the program as it is.

Note: A few of the library commands use callback functions, which you have to define yourself and which are thus not located in the library (e.g., Uwin_Open from the EasyGem Library). If you now call library commands using [Step into], the program pointer does not jump into the library but could land in your callback routine if this is just now being called from within the library.
Unless you intend this action, it is better to use the button [Single Step] to avoid any such confusion.


[Step out]
This button can be selected only if a program has been stopped. It serves to trigger a procedure or function without having to execute all commands within the procedure or function in individual steps. To be exact, the program execution is continued until the debugger meets up with a RETURN, END_PROC, END_FN, or EXIT, which is located at the same hierarchy level as the program pointer was when this button was clicked.
After the button has been selected, the program pointer is then located directly behind the command used to call the procedure or function. The same applies if subprograms are called via GOSUB commands.

Note: If you are using this button while the program pointer is not located in a subprogram, it will have the same effect as [Continue], because no RETURN commands are located in the uppermost hierarchy level.


[Single Step]
This button can be selected only if a program has been stopped. This function is similar to the function of the [Step into] button with the one difference that subprogram calls are always interpreted like BASIC commands and therefore is never be stopped in subprograms.


[Animation]
This button can be selected only if a program has been stopped. This will continue the program in slow motion. The standard settings ensures that the debugger executes max. one command per second. This interval can also be changed using the dialog box 'Debugger Settings'.


[Terminate]
This button can always be selected. It may be used to terminate the program controlled by the debugger at any time. This closes the window for the Debugger Control automatically.



Attention: A few Omikron Basic commands wait for user input before continuing the program. Since the debugger can halt a program only in-between two commands but not actually in the middle of a command, the debugging command in these cases does not become effective until the command has been concluded.
These are the Omikron Basic commands INPUT, LINE INPUT, FORM_ALERT, and FILESELECT or FSEL INPUT, as well as the EasyGem commands Easy_Alert,Easy_Fsel, Easy_Fnav, and Pick_Color.
For example, if your program is in an INPUT instruction and you click on [Terminate], the program is not terminated until the user has exited the input instruction with [Return].




STOP Commands and Breakpoints

If you stop your program using the Debugger Control, the spot where the program stops is more or less selected at random. But there are two other possibilities, which will halt the program in a specifically selected location.

STOP Command
Even before starting your program with 'Debug' just insert a STOP command at the location where the program is to stop. Those who used to work with BASIC interpreters are probably familiar with this method. The STOP command halts a program without deleting variable and memory contents.You can then continue the program in a targeted manner using the Debugger Control.

Attention: The STOP command functions only if the program has been started with 'Debug'. A normally compiled program using the STOP command will lead to an error message with subsequent termination of the program.


Breakpoints
Breakpoints are additional stopping points, which you can include in your program. Contrary to STOP commands, breakpoints cannot be set until the program has been started with 'Debug'. Just pull the source code window to the front, press the [Ctrl] key, and click on the spot in the source code where the breakpoint is to be set. A small ellipsis appears in the color of the commentary lines. As soon as the program pointer is now running across this spot, the program is halted and you can use the Debugger Control for a detailed analysis. If you click on a breakpoint again, it is removed.
You can set as many breakpoints as desired. Contrary to STOP commands, breakpoints are only effective during a debugging phase. After the program is finished they are automatically deleted. They also cannot be copied to the clipboard or saved. If you would like to insert permanent breakpoints into your program you have to use the STOP command.

Note: You cannot set breakpoints in all locations of a program because some lines, such as a commentary line, are skipped by the compiler and some BASIC commands serve only to structurize but do not generate an object code. In these cases, the breakpoint appears in the next possible location before the position on which you clicked.

Attention: If you are using the EasyGem Library or have programmed your own event handling routine, your program cannot restore the contents of windows once it has been halted by the debugger.
This does not apply to Omikron Basic output windows. These are managed by a small assembler program, which continues to run even when the BASIC program has been halted by the debugger.




Memory Windows

Memory windows can be opened with the menu options carrying the same name from the 'Program' menu. Memory windows are used to view the content of RAM memory.
There is an input line in the upper left corner of the window, which may be used to specify at which logical address the memory content view is to start. The address can be prefixed and indicated in all number systems supported by Omikron Basic (e.g., $A6553 or 123456). Pushing [Return] shows the memory content starting with the specified address.
If the specified memory range does not exist, an error message appears and nothing is shown.
The type of display can be set via the Debugger Settings.




Variable Windows

You can enter the names of variables into the variable windows. Their content is then shown in the same window. For string variables the length of the strings are indicated as well.
Only constants or individual variables may be used for the indices of field variables (e.g., A#(X,Y) or A#(1,3)). As long as a program has not been started with 'Debug' the content of variables is, of course, 0 and an empty string for strings.
The number system to display the variable content can be set with a prefix. For example, the input of $Tex$ would output the content of the string Tex$ as a byte sequence in hex numbers and not as a character string. Similarly, the input of %A would result in the variable content A as a binary number. According to the default setting, the output of numerical variables is always in the form of decimal numbers and strings are always output as character strings.Strings can also be output as a decimal byte sequence by prefixing with the number symbol "#."This is of special interest if one is interested in the ASCII values of a string.
Block functions also work in variable windows. Instead of typing in the desired variables, you can transfer them from the program window using 'Copy' and 'Paste'.
Variable windows can also be saved, reloaded, or printed. The format for saving is ASCII. If they are reloaded, the variables are assigned to the currently topmost program window.
If a variable does not exist or a field element is not located within the dimensioned range, the line is marked in red to indicate an error line (red is the default setting).
A variable window always refers to a specific program window. If the window is closed, all linked variable windows are automatically closed as well, because the variables contained in the program window are no longer defined.
Modifications to the variable windows can be set in the Debugger Settings dialog.


Debugger Settings

It is, of course, possible to configure all important functions of the debugger individually. For this purpose, the dialog 'Debugger Settings' called from the dialog box 'Preferences' is used. The dialog box can remain open while you work with the debugger so that you can change the settings, e.g., the animation speed, during a running animation process.
The dialog is divided into the areas Memory Windows and General.

Memory Windows:
A Memory Window can be used to view the content of the RAM memory. The display can vary.

Bytes per group:
This popup menu is used to specify how many bytes are displayed next to each other until a blank space is inserted.

Bytes per line:
This value indicates how many bytes are displayed in one line.

Display addresses as:
Here, you can select whether the memory address shown on the left side of the memory window is to be displayed as a decimal or hexadecimal number.

Display bytes as:
This popup menu makes it possible to choose between different number systems (from binary to hexadecimal) for the display of the memory content.

Show character string:
Since the content of an 8-bit memory cell can also always be interpreted with the corresponding ASCII code, the memory content can thus also be interpreted as a sequence of ASCII characters. This usually serves a purpose only if the examined memory location really contains actual pieces of text (e.g., string variable content). Otherwise, the displayed font is more reminiscent of old Egyptian hieroglyphs than the content of a computer memory ;-).
Use this checkbox to activate or deactivate the additional display of the memory area in the form of a character string.


General:
This area offers additional settings for the debugging functions.

Characters per line in variable windows:
Just as for program windows, the number of characters to be max. displayed in one line may be set by users for Variable Windows as well. The default setting of 255 characters usually suffices. If you need to track longer strings in the variable window, you might have to increase this value.

Animation delay [in msec]:
The value set here indicates the number of milliseconds the debugger waits between two commands if the program is executed in animation mode. A value of 1000 thus means that a command is executed every second. The execution time of the command itself is deducted from the delay time. For example, if a command itself takes 0.3 seconds to be executed, the debugger waits only another 0.7 seconds until executing the next command.

Permanently update variable windows:
The content of variable windows is usually only updated when the program is in animation mode or is run in single steps. With this checkbox you specify that the variable windows are always to show the current variable contents even when the program is in normal mode.

Permanently update memory windows:
This checkbox has the same effect but applies to memory windows.

Step into PROCs and FNs in case of animation:
Procedures and functions are usually treated like BASIC commands when in animation mode. This checkbox can be used to specify to jump into the procedures and functions and to execute the therein contained individual commands step by step.


There are three action buttons at the end of the dialog box:

[Execute]
The adjusted settings are executed and are immediately visible or effective. During animation you can therefore adjust the delay time and activate that time with this button.

[Undo]
The last adjusted setting is canceled.

[Cancel]
The dialog box is closed without accepting the adjusted settings.


Error Messages of the Debugger

The program "Program Name" has unexpectedly quit.
In some cases it can happen that a program to be debugged is terminated spontaneously. In these cases it is no longer possible to send a message to the debugger. The debugger in turn is not informed of the termination. If you now still try to apply debugger commands to the terminated program, you will receive this error message.


The addressed memory does not exist.
This error message appears if you try to display a non-existing memory range.



Practical Examples:

After we have described the debugger components, we would like to illustrate how to use the debugger by using some examples. The sample programs are equipped with line numbers for easier line reference. The line numbers are not required to run the program. All variables without postfix are of the long integer type according to the Omikron Basic editor default setting. If you have modified this setting, you have to consider this when transferring the program code.
The examples listed here are located in the folder DEMO:Debugger (Demo).


Example 1:

Open a new program window and transfer the following program to an empty window using the 'Copy' and 'Paste' functions. We have added an error to example 1, which we now will find using the debugger.

0 'This program calculates all prime numbers smaller
1 'than 121. The applied method is also known as the sieve of
2 'Eratosthenes. All prime numbers smaller than
3 '11=SQR(121) have to be known already. These are
4 'the numbers 2,3,5,7, which are stored in the DATA line
5 'and are read to the field Primes()at the beginning.
6 'For the numbers 11 to 120 it suffices if they
7 'are checked for divisibility by one of these
8 '4 prime numbers. If a number is divisible,
9 'a flag is set into the field No_Prime%F() and the
10 'number on the screen is shown inverted. At the end
11 'all numbers not inverted are the
12 'searched for prime numbers.
13
14 COMPILER "PRE_SIZE 500000"
15 COMPILER "BAS_MEM 500000"
16 COMPILER "OPW 340*240"
17 COMPILER "warnings off"
18
19 Prim_Dim=120:P_Dim=3
20 DIM Primes(P_Dim),No_Prime%F(Prim_Dim)
21 RESTORE Prime_Numbers:'Read all prime numbers <11=SQR(121)
22 FOR J=0 TO 2:READ Primes(J):NEXT J
23 USING "####":Columns=10:Lines=12
24 'Starting number, end number, rows, columns
25 Display_Numbers 2,Prim_Dim,Columns,Lines
26 PRINT CHR$(27)+"p";:'Revers on.
27 X=1:Y=0
28 'Test if the numbers 2 to Prim_Dim can be divided by
29 'the prime numbers < 11=SQR(121).
30 FOR I=2 TO Prim_Dim
31
 FOR J=0 TO 3
32
 IF I<>Primes(J) THEN
33
  'Test only numbers not already
34
  'known as primes.
35
  IF I MOD Primes(J)=0 THEN
36
   'Print number in reverse, no prime.
37
   No_Prime%F(I)=-1:PRINT @(Y,X);MID$(STR$(I),2)
38
   EXIT :'Since I is no prime, the other
39
   'dividers do not have to be tested any more.
40
  ENDIF
41
 ENDIF
42
 NEXT J
43
 X+=4:IF (I-1) MOD Columns=0 THEN Y+=1:X=1:'Next line.
44 NEXT I
45 REPEAT COMPILER "EVENT" UNTIL 0
46 END
47
48 -Prime_Numbers:'The first 4 primes.
49 DATA 2,3,5,7
50
51 'This procedure writes the numbers from Z0 to Z1 to a
52 'rectangular scheme with C columns and L rows on the screen.
53 DEF PROC Display_Numbers(Z0,Z1,C,L)
54
 LOCAL I,J
55
 CLS
56
 FOR J=0 TO L-1
57
  FOR I=0 TO C-1
58
   PRINT Z0;:Z0+=1
59
   IF Z0>Z1 THEN EXIT
60
  NEXT I
61
  PRINT :IF Z0>Z1 THEN EXIT
62
 NEXT J
63 END_PROC

Start the program first with 'Compile & Run'. The program is to calculate the prime numbers between 11 and 120, and it is assumed that the primes < 11 are already known. As you can see, the program runs normal and does not issue any error messages, but it also calculates incorrectly because the number 49 is marked as a prime even though it is divisible by 7.

Quit the program and restart it once more using the menu option 'Debug'. The program stops on line 35 and issues the error message "integer overflow." Click on [OK]. You are now in the debugger. The position where the program stopped is marked with a small triangle.
To ascertain the type of error that occurred open a new variable window. Enter I, J, and Primes(J)into the window and press [Return] after each input so that the variables appear in different lines. As can be seen, I has the starting value 2, J the end value 3, and Primes(J)=0, so that the operation I MOD Primes(J) is not defined and leads to the "Integer overflow."
Just one glance at line 22 shows that an incorrect end value is specified for the control variable J in the READ loop (2 instead of 3).
Now click on [Terminate] in the Debugger Control. Then add a STOP command to line 18 and correct the wrong end value in line 22 (change 2 to 3).
Then restart the program with 'Debug'. The program is now halted directly behind the STOP command. Primes(J) is depicted as an error line in the variable window. This is because this field has not been dimensioned yet. Now advance one step at a time by clicking on [Single Step] in the Debugger Control. After the DIM command in line 20 has been executed, Primes(J)=0 is displayed correctly in the variable window. If you now proceed with the READ loop step by step until finished, you can view on the monitor how the prime numbers are read into the field Primes() one after the other. At the end of the loop, Primes(J) is again depicted as an error because J is now 4 and Primes(J) has only been dimensioned up to three.

Once you reach line 25 you can decide whether you want to execute the procedure Display_Numbers as a command with [Single Step] or if you would like to jump into the procedure with [Step into]. We decided to use [Step into] to demonstrate the functions of the debugger.
Since PRINT output is directed to the Omikron Basic output window from within the procedure Display_Numbers, you should move aside the program window so that you can see the output window.
Of course, now you could execute all commands in the procedure in individual steps but since nothing spectacular is happening here we would like to take this opportunity to demonstrate another function of the debugger. Click on [Animation]. The debugger now executes all commands in slow motion. You can now track how the program pointer progresses through both of the loops and how the numbers are written to the Omikron Basic output window. Once you have seen enough, click on [Stop]. The program stops at the current location. If you now click on [Step out] all commands within the procedure are executed and the program is not stopped until line 26, behind the procedure call, has been reached.

Now keep the [Ctrl] key depressed and clicked on the FOR in line 31 with your mouse. This sets a breakpoint in front of the FOR depicted as an ellipsis. Click on [Continue]. The program pointer stops exactly on the breakpoint. If you now click on [Continue] several times, you can watch how every individual number is examined to see if it is a prime number. The variable window now shows the variable values of I and the other variables and track in the Omikron Basic output window how the individual numbers are marked as non-primes.
Now keep the [Ctrl] key depressed and clicked on the breakpoint in line 31. This removes the breakpoint. Set a new breakpoint in line 43 behind the THEN. Then click on [Continue]. The program will now always stop when a new line is started.
Remove the breakpoint again and click on [Continue]. All non-primes are marked and the program is in the waiting loop in line 45.

Click on [Stop]. The program halts in the waiting loop. This works only because the waiting loop contains an EVENT call (COMPILER "EVENT" or Easy_Mesag with EasyGem programming). If your program is in a loop without any EVENT calls, you can still stop the program using [Ctrl] +[C].

Click on [Terminate]. Eratosthenes is terminated, the Debugger Control is hided, and you can edit the source code again to calculate additional prime numbers, for example.



Example 2:

The second example does not contain any errors and serves only to illustrate the debugger functions further. Again, open a new program window and transfer the following program to an empty window using the 'Copy' and 'Paste' functions. Then you have to load the Extension Library with the menu option 'Merge LIBRARY'.

0 'This program calculates the relative frequencies of the numbers
1 'from 0 to 35, as they occur during roulette. For this purpose,
2 'a data set is generated as a demo using a random
3 'generator, which is then written to the "RouletteData"
4 ' file.
5 'Then the file is loaded and statistically evaluated.
6 'Of course, in reality one has to use the actual numbers
7 'of a real roulette table as the data set to then use
8 'the statistical evaluation to ascertain if mechanical
9 'inhomogeneities of the device causes some numbers to be
10 'selected more often than others.
11 'Before you now gamble away all of your money, you should,
12 'however, consider that the statistical deviations are
13 'still rather large with only 10000 numbers ;-)
14
15 COMPILER "PRE_SIZE 1000000"
16 COMPILER "BAS_MEM 500000"
17 COMPILER "OPW 200*560"
18 COMPILER "warnings off"
19
20 Extension_Init
21 STOP
22 Items=35:Max_Numbers=10000
23 Generate_Data Items,Max_Numbers
24 'The data evaluation starts here.
25 DIM Quantity(Items)
26 'Data are first read.
27 OPEN "U",1,FN Get_Fsspec$(0,0,"RouletteData")
28 Max_Numbers=LOF(1)
29 Adr=MEMORY(Max_Numbers)
30 'Transfer the entire data contents to the memory area.
31 GET 1,Adr,Max_Numbers
32 CLOSE 1
33 'Statistical Evaluation.
34 FOR Ptr=Adr TO Adr+Max_Numbers
35
 N=PEEK(Ptr)
36
 Quantity(N)+=1
37 NEXT Ptr
38
39 Set_Print_Size 12
40 PRINT "Relative Frequency"
41 PRINT "Roulette Numbers in Percent"
42 PRINT
43 PRINT " Number";TAB(10);"Frequency"
44 Set_Print_Size 10
45 FOR N=0 TO Items
46
 P#=100*Quantity(N)/Max_Numbers:'Relative Frequency.
47
 PRINT USING "#####";N;TAB(10); USING "#####.##";P#
48 NEXT N
49
50 REPEAT COMPILER "EVENT" UNTIL 0
51 END
52
53 'This procedure generates a random data set
54 'and then writes it to the file "RouletteData"
55 DEF PROC Generate_Data(N,M)
56
 LOCAL Adr,Ptr
57
 Adr=MEMORY(M)
58
 FOR Ptr=Adr TO Adr+M
59
  POKE Ptr,RND(N+1)
60
 NEXT Ptr
61
 OPEN "U",1,FN Get_Fsspec$(0,0,"RouletteData")
62
 'Write the data set to a file as a whole.
63
 PUT 1,Adr,M
64
 CLOSE 1
65
 FRE Adr
66 END_PROC

Then start the program with 'Debug'. As expected, it stops in line 22, directly behind the STOP command. Now progress with [Step into] until you reach line 58. Open a new variable window and there enter $Adr. After pressing the [Return] key, the address of the memory block allocated in line 57 is depicted in the variable window as a hex number.
Open a new memory window and then the debugger settings using 'Mode/Preferences/Debugger'. In the now opened dialog, set 'Bytes per group' to 1, 'Bytes per line' to 8, 'Display bytes as' to decimal and 'Show character string' to off. Then click on [Execute]. The content of the memory window is now depicted in the form of three-digit decimal numbers. The character string display disappears. Now transfer the value shown in the variable window for Adr to the address field of the memory window using 'Copy' and 'Paste' and confirm with [Return].
If you now proceed through the program in individual steps, you can follow in the memory window how the random numbers are written to memory.
Return to the debugger settings and set the 'Animation delay' to 100. Then confirm with [Execute]. Make sure that the memory window and the window with the BASIC program remain visible and then click on [Animation] in the Debugger Control. You can now observe how the random numbers are written to memory every tenth of a second.
Once you have seen enough, click on [Stop] and then on [Step out]. This causes you to leave the procedure, and then you are located behind the call in line 25.

Set a new breakpoint in line 39 by pressing the [Ctrl] key and click in front of Set_Print_Size with your mouse button to then continue the program. The program stops in line 39. Now click on [Step into]. Although the next item to be executed is a procedure, the program pointer jumps to the next line because the procedure Set_Print_Size is located in a library.

Now bring the Omikron Basic output window to the front. Then click on [Animation]. You can now see how the statistical data are written to the Omikron Basic output window.


Example 3:

This example is a very simple program to easily show which values from MOUSEBUT and INKEY$ are returned if certain keys are pressed. Again, open a new program window and transfer the program to an empty window using the 'Copy' and 'Paste' function.

0 COMPILER "PRE_SIZE 500000"
1 COMPILER "BAS_MEM 500000"
2 COMPILER "OPW 320*200"
3 COMPILER "warnings off"
4
5 REPEAT
6
 Mb=MOUSEBUT
7
 Kbrd$=INKEY$
8
 COMPILER "EVENT"
9 UNTIL 0
10 END

Go to the debugger preferences and activate 'Permanently update variable windows'. Then click on [Execute]. Open a new variable window and enter the variables %Mb and %Kbrd$.

Then start the program with 'Debug'. If you now press the mouse button alone or together with any modifier key, you can immediately see in the variable window which bits are set. The same applies to all normal keys of the keyboard. The thus generated ASCII codes, virtual key codes, and modifier key codes are displayed in Kbrd$.



10. The Library Maker |Table of Contents

Support | Ordering | Start | Home: http://www.berkhan.de


© 1997-1999 Berkhan-Software